using AutoMapper;
using housingsafety.server.Helpers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Text.Json.Serialization;
using housingsafety.server.Entities;
using housingsafety.server.Filters;
using housingsafety.server.Repository.Banana;
using housingsafety.server.Repository.Banana.Models;
using housingsafety.server.Repository.Extensions;
using housingsafety.server.Security;
using housingsafety.server.SvcImpls;
using housingsafety.server.Svcs;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using NetTopologySuite.IO.Converters;
using NetTopologySuite;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using NetTopologySuite.Geometries;
using Hangfire;
using Hangfire.Dashboard.BasicAuthorization;
using Microsoft.AspNetCore.StaticFiles;
using System.Collections.Generic;
using Microsoft.Extensions.FileProviders;
using housingsafety.server.Controllers;
using FluentValidation.AspNetCore;
using Invio.Extensions.Authentication.JwtBearer;
using housingsafety.server.Hawk;
using housingsafety.server.Jobs;
using Modularity.AspNetCore.Configuration;
using System.Runtime.InteropServices;
using FFMpegCore;
using System.IO;
using housingsafety.server.Caching;
using housingsafety.server.Models;
using Quartz;
using Quartz.Impl;
using Quartz.Spi;
using housingsafety.server.Entities.DataModels;
using KS3;

namespace housingsafety.server
{
    /// <summary>
    /// ������
    /// </summary>
    public class Startup
    {
        /// <summary>
        /// ���캯��
        /// </summary>
        /// <param name="configuration">���ö�����</param>
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        /// <summary>
        /// ���ö�����
        /// </summary>
        public IConfiguration Configuration { get; }

        /// <summary>
        /// This method gets called by the runtime. Use this method to add services to the container.
        /// </summary>
        /// <param name="services"></param>
        public void ConfigureServices(IServiceCollection services)
        {
            var loggerFactory = LoggerFactory.Create(builder =>
            {
                builder.AddConsole();
            });
            var logger = (loggerFactory.CreateLogger<Startup>());

            //CSRF
            services.AddAntiforgery(options =>
            {
                options.Cookie.SameSite = SameSiteMode.Lax;
                options.HeaderName = "X-XSRF-TOKEN";
            });

            //��������
            services.AddOptions();

            // Enable CORS
            services.AddCors();
            services.AddControllers(c =>
                {
                    c.Filters.Add<DbAuthorizationFilter>();

                    //https://github.com/NetTopologySuite/NetTopologySuite.IO.GeoJSON/wiki/Using-NTS.IO.GeoJSON4STJ-with-ASP.NET-Core-MVC
                    // Prevent the following exception: 'This method does not support GeometryCollection arguments' 
                    // See: https://github.com/npgsql/Npgsql.EntityFrameworkCore.PostgreSQL/issues/585 
                    c.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Point)));
                    c.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Coordinate)));
                    c.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(LineString)));
                    c.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(MultiLineString)));
                })
                .AddJsonOptions(jsonOptions =>
                {
                    //https://github.com/NetTopologySuite/NetTopologySuite.IO.GeoJSON/wiki/Using-NTS.IO.GeoJSON4STJ-with-ASP.NET-Core-MVC
                    // this constructor is overloaded.  see other overloads for options.
                    jsonOptions.JsonSerializerOptions.Converters.Add(new GeoJsonConverterFactory());
                    jsonOptions.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
                })
                .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
                .AddFluentValidation(fv => fv.RegisterValidatorsFromAssembly(GetType().Assembly));

            // nothing to do with NTS.IO.GeoJSON4STJ specifically, but a recommended
            // best-practice is to inject this instead of using the global variable:
            services.AddSingleton(NtsGeometryServices.Instance);
            services.AddStaticData(Configuration);
            services.AddHttpClient();
            services.AddCache(Configuration, logger);
            services.AddHttpContextAccessor();
            services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
            services.AddJobWorkers(Configuration, logger);

            var serviceProvider = services.BuildServiceProvider();

            // 设置全局服务提供程序
           services.AddTransient(serviceProvider => new KS3Client("AKLTVb67SHyPTg2rM5KHvPBK", "OKDmwbeAYJah94yaby9Lbgcuu3MnAqN139Sq96gl")); 
           // services.AddTransient(serviceProvider => new KS3Client("AKLTJrfQNCNMQzOMcNQlT8VYRA", "OAHB1xZMd4sjTv2ULhYIniJXpLkxfGJBPzgSvLvD7XXBvO48TL3thga62yWRkKozMQ=="));
            services.AddJwt(Configuration);

            //services.AddSwagger(Configuration);

            services.AddAddressService(Configuration);


            services.AddRateLimit(Configuration);

            services.AddAliyunSms(Configuration);

            services.AddFileStore(Configuration);

            services.AddDataBaseOper(Configuration);

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

            services.AddSingleton<IImageCacheProvider, DiskImageCacheProvider>();
            services.AddSingleton<IImageStorageProvider, DiskImageStorageProvider>();
            services.AddSingleton<IStorageProvider, DiskStorageProvider>();
            
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.None;
            });

            var connectionString = Configuration.GetConnectionString("DefaultConnection");
            services.AddRepository<ZmDbContext>(options => options
            .UseNpgsql(connectionString, x => x.UseNetTopologySuite()));

            ConnectionBuilder.ConfigRegist(connectionString, DbType.Postgres);

            //���Ӷ�AutoMapper��֧�֣���������г����м̳��� Profile ����
            services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

            // To list physical files from a path provided by configuration:
            //var physicalProvider = new PhysicalFileProvider(Configuration.GetValue<string>("StoredFilesPath"));

            // To list physical files in the temporary files folder, use:
            //var physicalProvider = new PhysicalFileProvider(Path.GetTempPath());

            //services.AddSingleton<IFileProvider>(physicalProvider);
            services.Configure<FormOptions>(options =>
            {
                // Set the limit to 30 MB
                options.MultipartBodyLengthLimit = 150_428_800;//30_000_000;
            });

            services.AddAuthorization(options =>
            {
                options.AddPolicy(Policys.AdminOnly, policy =>
                    policy.Requirements.Add(new AdminOnlyRequirement()));
                
            });
            services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();

            // add other services and configure the MVC middleware
            services.AddScoped<IAuthorizationHandler, AdminOnlyHandler>();
            services.AddScoped<ITokenHelper, TokenHelper>();
            services.AddModuleServices(Configuration); // <-- Add this to add module's services into DI Container.
        }

        /// <summary>
        /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        /// </summary>
        /// <param name="app"></param>
        /// <param name="env"></param>
        /// <param name="dataContext"></param>
        /// <param name="logger"></param>
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ZmDbContext dataContext, ILogger<Startup> logger)
        {
            ServiceLocator.Instance = app.ApplicationServices;
           
            //app.UseIpRateLimiting();
            app.UseMiddleware<IpLimitMiddleware>();
            //support rewinding
            app.Use(async (context, next) =>
            {
                context.Request.EnableBuffering();
                await next();
            });
            if (env.IsDevelopment())
            {
                logger.LogWarning("Running on Development Mode...");
                app.UseDeveloperExceptionPage();
            }

            // migrate any database changes on startup (includes initial db creation)
            FlywayDatabase.MigrateDatabase(dataContext, env, logger);

            var pathBase = Configuration["AppSettings:UsePathBase"];
            app.UsePathBase(pathBase);

            // Add Hangfire Server and Dashboard support
            app.UseHangfireServer();
            //app.UseHangfireDashboard("/hangfire", new DashboardOptions
            //{
            //    Authorization = new[] { new HangfireAuthorizationFilter(Policys.AdminOnly) }
            //});
            //app.UseHangfireDashboard("/hangfire", new DashboardOptions
            //{
            //    DisplayStorageConnectionString = false,
            //    Authorization = new[] { new BasicAuthAuthorizationFilter(new BasicAuthAuthorizationFilterOptions
            //    {
            //        RequireSsl = false,
            //        SslRedirect = false,
            //        LoginCaseSensitive = true,
            //        Users = new []
            //        {
            //            new BasicAuthAuthorizationUser
            //            {
            //                Login = "admin",
            //                PasswordClear =  "admintest123"
            //            }
            //        }
            //    }) }
            //});
            //SystemJobs.Initialize(app.ApplicationServices);
            //// Run every week
            //RecurringJob.AddOrUpdate(() => SystemJobs.BackupDb(), Cron.Weekly);


       
            //RecurringJob.AddOrUpdate(() => new SystemJobs().StatHiddenDangerInfo_zm_xf_pc_detailed_t2(), "0 59 15 * * ?");

            //RecurringJob.AddOrUpdate(() => new HangfireController().StatHiddenDangerInfo("0",true), "0 47 10 * * ?");
            //RecurringJob.AddOrUpdate(() => new HangfireController().StatHiddenDangerInfo107("1", true), "0 47 10 * * ?");
            //RecurringJob.AddOrUpdate(() => new HangfireController().StatHiddenDangerInfo777("1", true), "0 47 10 * * ?");
            //RecurringJob.AddOrUpdate(() => vv1.StatHiddenDangerInfo_zm_xf_pc_detailed_t2(), "0 59 15 * * ?");
            //RecurringJob.AddOrUpdate(() => SystemJobs.StatHiddenDangerInfo(), "0 59 15 * * ?");
            //RecurringJob.AddOrUpdate(() => SystemJobs.StatHiddenDangerInfo107After(), "0 59 15 * * ?");
            ////RecurringJob.AddOrUpdate(() => SystemJobs.StatHiddenDangerInfo107Before(), "0 59 15 * * ?");
            //RecurringJob.AddOrUpdate(() => SystemJobs.StatHiddenDangerInfo777After(), "0 59 15 * * ?");
            //RecurringJob.AddOrUpdate(() => SystemJobs.StatHiddenDangerInfo777Before(), "0 59 15 * * ?");
            app.UseRouting();

            //配置url映射
            app.UseStaticFiles();
            app.UseStaticFiles(new StaticFileOptions()
            {
                FileProvider = new PhysicalFileProvider(
                    Path.Combine(Directory.GetCurrentDirectory(), @"")),
                RequestPath = new PathString("/StaticFiles")
            });



            app.UseStaticFiles();
            //#if DEBUG
            // global cors policy
            app.UseCors(x => x
                .AllowAnyMethod()
                .AllowAnyHeader()
                .SetIsOriginAllowed(origin => true) // allow any origin
                .AllowCredentials()); // allow credentials
//#endif

            //��Ȩ�м��
            app.UseAuthentication();
            //������֤�м��(�ȿӣ���Ȩ�м����������֤�м��֮ǰ)
            //3.x��������֤һ��Ҫ��UseRouting��UseEndpoints֮��
            app.UseAuthorization();
            // This should come after authentication but before any logging middleware.
            app.UseJwtBearerQueryString();

            //yw: 增加对静态示例瓦片的支持，支持客户端缓存，放在鉴权中间件之后，支持鉴权
            //IConfigurationSection config = Configuration.GetSection("staticData");
            //var staticDataConf = config.Get<StaticDataConf>();
            //var provider = new FileExtensionContentTypeProvider();
            //provider.Mappings.Clear();
            //provider.Mappings[".pbf"] = "application/vnd.mapbox-vector-tile";
            //app.UseStaticFiles(new StaticFileOptions
            //{
            //    ServeUnknownFileTypes = true,
            //    FileProvider = new PhysicalFileProvider(staticDataConf.VectorTilesRoot),
            //    RequestPath = $"/api/{ApiVersion.Version}/static-data",
            //    OnPrepareResponse = ctx =>
            //    {
            //        //生成瓦片的时候默认已经采用gzip压缩， 因此这里要告诉客户端，否则客户端无法解析
            //        ctx.Context.Response.Headers.Add("Content-Encoding", "gzip");

            //        //默认缓存有效期为7天，客户端不清理缓存则缓存会生效
            //        ctx.Context.Response.Headers.Append(
            //             "Cache-Control", $"public, max-age=604800");
            //    },
            //});

            app.UseHawk();

            //开启geoserver反向代理拦截
            app.UseGeoServerProxy();

            app.UseEndpoints(endpoints =>
            {
                //if (env.IsDevelopment())
                //{
                //    // ����ģʽ�½���auth
                //    endpoints.MapControllers().WithMetadata(new AllowAnonymousAttribute());
                //}
                //else
                {
                    endpoints.MapControllers();
                    endpoints.MapFallbackToFile("index.html");
                }
            });


            //Enable middleware to serve generated Swagger as a JSON endpoint.
            //app.UseSwagger(_ =>
            //{
            //    _.RouteTemplate = $"api/{ApiVersion.Version}/swagger/{{documentname}}/swagger.json";
            //});

            ////Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint
            //app.UseSwaggerUI(option =>
            //{
            //    option.SwaggerEndpoint($"/api/{ApiVersion.Version}/swagger/{ApiVersion.Version}/swagger.json", "zealmap api doc");
            //    option.RoutePrefix = $"api/{ApiVersion.Version}/swagger";
            //    option.DocumentTitle = "zealmap api doc";
            //});

            app.UseModulartiy(env); // <-- Call UseModularity with HostingEnvirorment parameter

            //ConfigFfmpeg(logger);
        }

        private bool ConfigFfmpeg(ILogger logger)
        {
            //https://ffbinaries.com/downloads
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                FFMpegOptions options = new FFMpegOptions
                {
                    RootDirectory = "./ffmpeg/linux64",
                    TempDirectory = "./ffmpeg/tmp"
                };
                FFMpegOptions.Configure(options);
                if (!File.Exists(options.FFmpegBinary()))
                {
                    logger.LogError("FFmpeg not found");
                    return false;
                }
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                FFMpegOptions options = new FFMpegOptions
                {
                    RootDirectory = "./ffmpeg/win64",
                    TempDirectory = "./ffmpeg/tmp"
                };
                FFMpegOptions.Configure(options);
                if (!File.Exists(options.FFmpegBinary()))
                {
                    logger.LogError("FFmpeg not found");
                    return false;
                }
            }
            else
            {
                logger.LogError("FFmpeg not supportted platform");
                return false;
            }

            return true;
        }

    }
}
